Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: argocd version --client tries to connect server #8341

Merged
merged 10 commits into from
Feb 10, 2022

Conversation

gdsoumya
Copy link
Member

@gdsoumya gdsoumya commented Feb 2, 2022

Signed-off-by: Soumya Ghosh Dastidar gdsoumya@gmail.com

All commands call the PreRun hook and try to startup the local API server even when it's not required leading to the errors noticed. The proposed solution is to move the local API server creation from the PreRun hook into the argocdclient.NewClientOrDie call which will make sure the server is created only when a client needs to access it.

#Fixes #7986

Checklist:

  • Either (a) I've created an enhancement proposal and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
  • The title of the PR states what changed and the related issues number (used for the release note).
  • I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
  • I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
  • Does this PR require documentation updates?
  • I've updated documentation as required by this PR.
  • Optional. My organization is added to USERS.md.
  • I have signed off all my commits as required by DCO
  • I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
  • My build is green (troubleshooting builds).

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
@codecov
Copy link

codecov bot commented Feb 2, 2022

Codecov Report

Merging #8341 (2db0076) into master (b009cdb) will increase coverage by 0.40%.
The diff coverage is 3.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #8341      +/-   ##
==========================================
+ Coverage   41.91%   42.31%   +0.40%     
==========================================
  Files         178      176       -2     
  Lines       23003    22797     -206     
==========================================
+ Hits         9641     9646       +5     
+ Misses      11979    11775     -204     
+ Partials     1383     1376       -7     
Impacted Files Coverage Δ
cmd/argocd/commands/account.go 0.00% <0.00%> (ø)
cmd/argocd/commands/admin/app.go 30.50% <0.00%> (ø)
cmd/argocd/commands/admin/dashboard.go 0.00% <0.00%> (ø)
cmd/argocd/commands/app.go 0.53% <0.00%> (-0.01%) ⬇️
cmd/argocd/commands/app_actions.go 0.00% <0.00%> (ø)
cmd/argocd/commands/cert.go 0.00% <0.00%> (ø)
cmd/argocd/commands/cluster.go 5.68% <0.00%> (ø)
cmd/argocd/commands/gpg.go 0.00% <0.00%> (ø)
cmd/argocd/commands/login.go 2.30% <0.00%> (ø)
cmd/argocd/commands/project.go 0.00% <0.00%> (ø)
... and 17 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b009cdb...2db0076. Read the comment docs.

@jessesuen
Copy link
Member

Based on #7986 (comment), the issue turns out to be a bit deeper. I think we need to improve the CLI in general such that the assumption of a valid kube context is only used when necessary. For example, is it possible to fix this such that argocd app doesn't fail with an invalid kube context?

@jessesuen
Copy link
Member

jessesuen commented Feb 2, 2022

I feel like the logic for starting startInProcessAPI should be moved out of the PersistentPreRunE that sets up the CLI commands. @alexmt can you think of a better place to conditionally start this?

EDIT: Nevermind I see this is conditionally added to commands that need to access the API server:

	command.AddCommand(NewCompletionCommand())
	command.AddCommand(headless.InitCommand(NewVersionCmd(&clientOpts), &clientOpts, nil, nil))
	command.AddCommand(headless.InitCommand(NewClusterCommand(&clientOpts, pathOpts), &clientOpts, nil, nil))
	command.AddCommand(headless.InitCommand(NewApplicationCommand(&clientOpts), &clientOpts, nil, nil))
	command.AddCommand(NewLoginCommand(&clientOpts))
	command.AddCommand(NewReloginCommand(&clientOpts))
	command.AddCommand(headless.InitCommand(NewRepoCommand(&clientOpts), &clientOpts, nil, nil))
	command.AddCommand(headless.InitCommand(NewRepoCredsCommand(&clientOpts), &clientOpts, nil, nil))
	command.AddCommand(NewContextCommand(&clientOpts))
	command.AddCommand(headless.InitCommand(NewProjectCommand(&clientOpts), &clientOpts, nil, nil))
	command.AddCommand(headless.InitCommand(NewAccountCommand(&clientOpts), &clientOpts, nil, nil))
	command.AddCommand(NewLogoutCommand(&clientOpts))
	command.AddCommand(headless.InitCommand(NewCertCommand(&clientOpts), &clientOpts, nil, nil))
	command.AddCommand(headless.InitCommand(NewGPGCommand(&clientOpts), &clientOpts, nil, nil))
	command.AddCommand(admin.NewAdminCommand())

@jessesuen
Copy link
Member

I think I've come to the conclusion the only way to fix this for argocd app, argocd repo, etc... is to remove the call to headless.InitCommand outside of root.go and instead move them inside the app, repo, etc... for each subcommand. Similar to

headless.InitCommand(cmd, clientOpts, &port, &address)

@alexmt any better ideas?

@jessesuen
Copy link
Member

Actually, I think the cleaner approach would be to create a wrapper around argocdclient.NewClientOrDie(clientOpts) which starts up the throwaway local API server depending on if clientOpts.Core is set.

@gdsoumya
Copy link
Member Author

gdsoumya commented Feb 2, 2022

For commands like the version --client, it doesn't have any subcommands and probably still needs hardcoded checks in InitCommand.

Also I took a look at how cobra parses the command and for commands with subcommands, it appears that len(cmd.Commands())>0 (subcommand count) if that command is called without calling a subcommand. On the other hand, if the subcommand is called cmd actually points to the subcommand and not the parent command which means that len(cmd.Command()) points to the no. of commands that are children of the current subcommand.

With this if we can make an assumption that all top-level commands will only perform actions when a subcommand is called and only display the HelpFunc() when called alone (which is the case for all the commands as far as I can see) we could put a check in the PreRun hook:

if len(cmd.Commands())>0{
  return nil
  }

This would skip the hook execution if a top-level command is called without the subcommands and will display the help data.

@gdsoumya
Copy link
Member Author

gdsoumya commented Feb 2, 2022

This is a more generalized solution and will work for any depth of subcommands but if there are subcommands without any more children commands that need to skip this hook that would need to an extra check like the version command.

@gdsoumya
Copy link
Member Author

gdsoumya commented Feb 2, 2022

Actually, I think the cleaner approach would be to create a wrapper around argocdclient.NewClientOrDie(clientOpts) which starts up the throwaway local API server depending on if clientOpts.Core is set.

This is actually better as it only executes when a call to the server is needed.

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
@gdsoumya
Copy link
Member Author

gdsoumya commented Feb 2, 2022

@jessesuen have updated the PR according to your suggestion, let me know if this is good.

Copy link
Member

@jessesuen jessesuen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you group the imports such that we have:

  • standard libs in one block
  • 3rd party libs in 2nd block
  • github.com/argoproj/argo-cd in the third block

gdsoumya and others added 4 commits February 3, 2022 11:48
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Signed-off-by: Soumya Ghosh Dastidar <soumya@akuity.io>
@@ -72,7 +74,7 @@ has appropriate RBAC permissions to change other accounts.
c.HelpFunc()(c, args)
os.Exit(1)
}
acdClient := argocdclient.NewClientOrDie(clientOpts)
acdClient := headless.NewClientOrDie(clientOpts, initialize.RetrieveContextIfChanged(c.Flag("context")))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do this a little bit cleaner. Everywhere we repeat a call to initialize.RetrieveContextIfChanged when initializing the client. i.e.:

headless.NewClientOrDie(clientOpts, initialize.RetrieveContextIfChanged(c.Flag("context")))

Can we somehow improve/reduce this to:

headless.NewClientOrDie(clientOpts)

Can we update the clientOpts to have a .context field so we don't have to parse this everywhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially thinking of doing that but this specific context is only required when we need to create the local server and the CLientOptions struct is a more general client pkg data that might be used by other API clients and binaries so I felt it might be better to keep it specific to the cli only instead of modifying the original struct. But we can add it in there too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. But unfortunately, there's already a clientOpts.Core which is a useless flag to the generic argocd client package. So for better or worse, we already have CLI specific flag which made its way into in clientOpts.

If we want to be slightly cleaner about this, we could preserve the existing API client constructor. e.g.:

apiclient.NewClientOrDie(clientOpts)

And introduce a CLI specific constructor that accepts the cobra.Command as a second argument:

headless.NewClientOrDie(clientOpts, c)

The headless.NewClientOrDie would call:

initialize.RetrieveContextIfChanged(c.Flag("context"))
...
apiclient.NewClientOrDie(clientOpts)

Then we don't have to introduce more useless options to clientOpts which aren't really used by the API client package. And we could even remove the Core option from clientOpts. WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good to me, also we'll shift the headless.NewClientOrDie to the CLI pkg so it will not technically change anything in the apiclient pkg.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines 11 to 33
"github.com/alicebob/miniredis/v2"
"github.com/go-redis/redis/v8"
"github.com/golang/protobuf/ptypes/empty"
log "github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
cache2 "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/utils/pointer"

"github.com/argoproj/argo-cd/v2/pkg/apiclient"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
repoapiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/server"
servercache "github.com/argoproj/argo-cd/v2/server/cache"
"github.com/argoproj/argo-cd/v2/util/cache"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/argo-cd/v2/util/cli"
"github.com/argoproj/argo-cd/v2/util/io"
kubeutil "github.com/argoproj/argo-cd/v2/util/kube"
"github.com/argoproj/argo-cd/v2/util/localconfig"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might be bringing in too many dependencies into pkg/apiclient, which is meant to be a library/SDK. I think this package still belongs next to CLI code rather than library code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are okay with shifting the NewClientOrDie() to the CLI (which should be okay because it's only used there) we can move this pkg to the cli part. NewClientOrDie needs to access the headless methods which if kept in the apiClient pkg separate from headless in cli, causes import cycles.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are okay with shifting the NewClientOrDie() to the CLI (which should be okay because it's only used there) we can move this pkg to the cli part.

Spoke with @alexmt and he agrees that we should move this logic into CLI specific package to keep dependencies of pkg/apiclient leaner.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will shift it to CLI

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Signed-off-by: Soumya Ghosh Dastidar <soumya@akuity.io>
Copy link
Member

@jessesuen jessesuen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much cleaner approach. Thank you!

Comment on lines 460 to 468
// NewClientOrDie creates a new API client from a set of config options, or fails fatally if the new client creation fails.
func NewClientOrDie(opts *ClientOptions) Client {
client, err := NewClient(opts)
if err != nil {
log.Fatal(err)
}
return client
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we don't use this anymore in the CLI, but since it exists in pkg i think we may want to keep this for other projects importing argo-cd as a library.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly, that's why I did not remove this incase someone else is already using it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait according to this diff. You did remove this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, I thought I moved the code around in the same pkg. Let me add it back.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted it back.

Signed-off-by: Soumya Ghosh Dastidar <soumya@akuity.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

argocd version --client does not work offline in --core mode
4 participants